<!DOCTYPE html>
<html lang="en">

<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
	<meta name="keywords" content="Seata、Config" />
	<meta name="description" content="Seata 可以支持多个第三方配置中心，那么 Seata 是如何同时兼容那么多个配置中心的呢？" />
	<!-- 网页标签标题 -->
	<title>Seata 配置中心实现原理</title>
  <link rel="shortcut icon" href="/img/seata_logo_small.jpeg"/>
	<link rel="stylesheet" href="/build/blogDetail.css" />
</head>
<body>
	<div id="root"><div class="blog-detail-page" data-reactroot=""><header class="header-container header-container-normal"><div class="header-body"><a href="/zh-cn/index.html"><img class="logo" src="//img.alicdn.com/tfs/TB1gqL1w4D1gK0jSZFyXXciOVXa-1497-401.png"/></a><div class="search search-normal"><span class="icon-search"></span></div><span class="language-switch language-switch-normal">En</span><div class="header-menu"><img class="header-menu-toggle" src="https://img.alicdn.com/tfs/TB14eEmw7P2gK0jSZPxXXacQpXa-38-32.png"/><ul><li class="menu-item menu-item-normal"><a href="/zh-cn/index.html" target="_self">首页</a></li><li class="menu-item menu-item-normal"><a href="/zh-cn/docs/overview/what-is-seata.html" target="_self">文档</a></li><li class="menu-item menu-item-normal"><a href="/zh-cn/docs/developers/developers_dev.html" target="_self">开发者</a></li><li class="menu-item menu-item-normal menu-item-normal-active"><a href="/zh-cn/blog/index.html" target="_self">博客</a></li><li class="menu-item menu-item-normal"><a href="/zh-cn/community/index.html" target="_self">社区</a></li><li class="menu-item menu-item-normal"><a href="/zh-cn/blog/download.html" target="_self">下载</a></li></ul></div></div></header><section class="blog-content markdown-body"><h1>前言</h1>
<p>Seata 可以支持多个第三方配置中心，那么 Seata 是如何同时兼容那么多个配置中心的呢？下面我给大家详细介绍下 Seata 配置中心的实现原理。</p>
<h1>配置中心属性加载</h1>
<p>在 Seata 配置中心，有两个默认的配置文件：</p>
<p><img src="https://gitee.com/objcoding/md-picture/raw/master/img/20191211193041.png" alt=""></p>
<p>file.conf 是默认的配置属性，registry.conf 主要存储第三方注册中心与配置中心的信息，主要有两大块：</p>
<pre><code class="language-json">registry {
  # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
  # ...
}

config {
  # file、nacos 、apollo、zk、consul、etcd3
  type = "file"
  nacos {
    serverAddr = "localhost"
    namespace = ""
  }
  file {
    name = "file.conf"
  }
  # ...
}
</code></pre>
<p>其中 registry 为注册中心的配置属性，这里先不讲，config 为配置中心的属性值，默认为 file 类型，即会加载本地的 file.conf 里面的属性，如果 type 为其它类型，那么会从第三方配置中心加载配置属性值。</p>
<p>在 config 模块的 core 目录中，有个配置工厂类 ConfigurationFactory，它的结构如下：</p>
<p><img src="https://gitee.com/objcoding/md-picture/raw/master/img/20191210211022.png" alt=""></p>
<p>可以看到都是一些配置的静态常量：</p>
<p>REGISTRY_CONF_PREFIX、REGISTRY_CONF_SUFFIX：配置文件名、默认配置文件类型；</p>
<p>SYSTEM_PROPERTY_SEATA_CONFIG_NAME、ENV_SEATA_CONFIG_NAME、ENV_SYSTEM_KEY、ENV_PROPERTY_KEY：自定义文件名配置变量，也说明我们可以自定义配置中心的属性文件。</p>
<p>ConfigurationFactory 里面有一处静态代码块，如下：</p>
<p>io.seata.config.ConfigurationFactory</p>
<p><img src="https://gitee.com/objcoding/md-picture/raw/master/img/20191211102702.png" alt=""></p>
<p>根据自定义文件名配置变量找出配置文件名称与类型，如果没有配置，默认使用 registry.conf，FileConfiguration 是 Seata 默认的配置实现类，如果为默认值，则会更具  registry.conf 配置文件生成 FileConfiguration 默认配置对象，这里也可以利用 SPI 机制支持第三方扩展配置实现，具体实现是继承 ExtConfigurationProvider 接口，在<code>META-INF/services/</code>创建一个文件并填写实现类的全路径名，如下所示：</p>
<p><img src="https://gitee.com/objcoding/md-picture/raw/master/img/20191211194643.png" alt=""></p>
<h1>第三方配置中心实现类加载</h1>
<p>在静态代码块逻辑加载完配置中心属性之后，Seata 是如何选择配置中心并获取配置中心的属性值的呢？</p>
<p>我们刚刚也说了 FileConfiguration 是 Seata 的默认配置实现类，它继承了 AbstractConfiguration，它的基类为 Configuration，提供了获取参数值的方法：</p>
<pre><code class="language-java"><span class="hljs-function"><span class="hljs-keyword">short</span> <span class="hljs-title">getShort</span><span class="hljs-params">(String dataId, <span class="hljs-keyword">int</span> defaultValue, <span class="hljs-keyword">long</span> timeoutMills)</span></span>;
<span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">getInt</span><span class="hljs-params">(String dataId, <span class="hljs-keyword">int</span> defaultValue, <span class="hljs-keyword">long</span> timeoutMills)</span></span>;
<span class="hljs-function"><span class="hljs-keyword">long</span> <span class="hljs-title">getLong</span><span class="hljs-params">(String dataId, <span class="hljs-keyword">long</span> defaultValue, <span class="hljs-keyword">long</span> timeoutMills)</span></span>;
<span class="hljs-comment">// ....</span>
</code></pre>
<p>那么意味着只需要第三方配置中心实现该接口，就可以整合到 Seata 配置中心了，下面我拿 zk 来做例子：</p>
<p>首先，第三方配置中心需要实现一个 Provider 类：</p>
<p><img src="https://gitee.com/objcoding/md-picture/raw/master/img/20191211200155.png" alt=""></p>
<p>实现的 provider 方法如其名，主要是输出具体的 Configuration 实现类。</p>
<p>那么我们是如何获取根据配置去获取对应的第三方配置中心实现类呢？</p>
<p>在 Seata 项目中，获取一个第三方配置中心实现类通常是这么做的：</p>
<pre><code class="language-java">Configuration CONFIG = ConfigurationFactory.getInstance();
</code></pre>
<p>在 getInstance() 方法中主要是使用了单例模式构造配置实现类，它的构造具体实现如下：</p>
<p>io.seata.config.ConfigurationFactory#buildConfiguration：</p>
<p><img src="https://gitee.com/objcoding/md-picture/raw/master/img/20191211102905.png" alt=""></p>
<p>首先从 ConfigurationFactory 中的静态代码块根据 registry.conf 创建的 CURRENT_FILE_INSTANCE 中获取当前环境使用的配置中心，默认为为 File 类型，我们也可以在 registry.conf 配置其它第三方配置中心，这里也是利用了 SPI 机制去加载第三方配置中心的实现类，具体实现如下：</p>
<p><img src="https://gitee.com/objcoding/md-picture/raw/master/img/20191211205127.png" alt=""></p>
<p>如上，即是刚刚我所说的 ZookeeperConfigurationProvider 配置实现输出类，我们再来看看这行代码：</p>
<pre><code class="language-java">EnhancedServiceLoader.load(ConfigurationProvider.class,Objects.requireNonNull(configType).name()).provide();
</code></pre>
<p>EnhancedServiceLoader 是 Seata SPI 实现核心类，这行代码会加载 <code>META-INF/services/</code>和 <code>META-INF/seata/</code>目录中文件填写的类名，那么如果其中有多个配置中心实现类都被加载了怎么办呢？</p>
<p>我们注意到 ZookeeperConfigurationProvider 类的上面有一个注解：</p>
<pre><code class="language-java"><span class="hljs-meta">@LoadLevel</span>(name = <span class="hljs-string">"ZK"</span>, order = <span class="hljs-number">1</span>)
</code></pre>
<p>在加载多个配置中心实现类时，会根据 order 进行排序：</p>
<p>io.seata.common.loader.EnhancedServiceLoader#findAllExtensionClass：</p>
<p><img src="https://gitee.com/objcoding/md-picture/raw/master/img/20191211210438.png" alt=""></p>
<p>io.seata.common.loader.EnhancedServiceLoader#loadFile：</p>
<p><img src="https://gitee.com/objcoding/md-picture/raw/master/img/20191211210347.png" alt=""></p>
<p>这样，就不会产生冲突了。</p>
<p>但是我们发现 Seata 还可以用这个方法进行选择，Seata 在调用 load 方法时，还传了一个参数：</p>
<pre><code class="language-java">Objects.requireNonNull(configType).name()
</code></pre>
<p>ConfigType 为配置中心类型，是个枚举类：</p>
<pre><code class="language-java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">enum</span> ConfigType {
  File, ZK, Nacos, Apollo, Consul, Etcd3, SpringCloudConfig, Custom;
}
</code></pre>
<p>我们注意到，LoadLevel 注解上还有一个 name 属性，在进行筛选实现类时，Seata 还做了这个操作：</p>
<p><img src="https://gitee.com/objcoding/md-picture/raw/master/img/20191211211210.png" alt=""></p>
<p>根据当前 configType 来判断是否等于 LoadLevel 的 name 属性，如果相等，那么就是当前配置的第三方配置中心实现类。</p>
<h1>第三方配置中心实现类</h1>
<p>ZookeeperConfiguration 继承了 AbstractConfiguration，它的构造方法如下：</p>
<p><img src="https://gitee.com/objcoding/md-picture/raw/master/img/20191211202510.png" alt=""></p>
<p>构造方法创建了一个 zkClient 对象，这里的 FILE_CONFIG 是什么呢？</p>
<pre><code class="language-java"><span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> Configuration FILE_CONFIG = ConfigurationFactory.CURRENT_FILE_INSTANCE;
</code></pre>
<p>原来就是刚刚静态代码块中创建的 registry.conf 配置实现类，从该配置实现类拿到第三方配置中心的相关属性，构造第三方配置中心客户端，然后实现 Configuration 接口时：</p>
<p><img src="https://gitee.com/objcoding/md-picture/raw/master/img/20191211203735.png" alt=""></p>
<p>就可以利用客户端相关方法去第三方配置获取对应的参数值了。</p>
<h1>第三方配置中心配置同步脚本</h1>
<p>上周末才写好，已经提交 PR 上去了，还处于 review 中，预估会在 Seata 1.0 版本提供给大家使用，敬请期待。</p>
<p>具体位置在 Seata 项目的 script 目录中：</p>
<p><img src="https://gitee.com/objcoding/md-picture/raw/master/img/20191211212141.png" alt=""></p>
<p>config.txt 为本地配置好的值，搭建好第三方配置中心之后，运行脚本会将 config.txt 的配置同步到第三方配置中心。</p>
<h1>作者简介</h1>
<p>张乘辉，目前就职于中通科技信息中心技术平台部，担任 Java 工程师，主要负责中通消息平台与全链路压测项目的研发，热爱分享技术，微信公众号「后端进阶」作者，技术博客（<a href="https://objcoding.com/">https://objcoding.com/</a>）博主，Seata Contributor，GitHub ID：objcoding。</p>
</section><footer class="footer-container"><div class="footer-body"><img src="//img.alicdn.com/tfs/TB1dGrSwVT7gK0jSZFpXXaTkpXa-4802-1285.png"/><p class="docsite-power">website powered by docsite</p><div class="cols-container"><div class="col col-12"><h3>愿景</h3><p>Seata 是一款阿里巴巴开源的分布式事务解决方案，致力于在微服务架构下提供高性能和简单易用的分布式事务服务。</p></div><div class="col col-6"><dl><dt>文档</dt><dd><a href="/zh-cn/docs/overview/what-is-seata.html" target="_self">Seata 是什么？</a></dd><dd><a href="/zh-cn/docs/user/quickstart.html" target="_self">快速开始</a></dd><dd><a href="https://github.com/seata/seata.github.io/issues/new" target="_self">报告文档问题</a></dd><dd><a href="https://github.com/seata/seata.github.io" target="_self">在Github上编辑此文档</a></dd></dl></div><div class="col col-6"><dl><dt>资源</dt><dd><a href="/zh-cn/blog/index.html" target="_self">博客</a></dd><dd><a href="/zh-cn/community/index.html" target="_self">社区</a></dd></dl></div></div><div class="copyright"><span>Copyright © 2019 Seata</span></div></div></footer></div></div>
	<script src="https://f.alicdn.com/react/15.4.1/react-with-addons.min.js"></script>
	<script src="https://f.alicdn.com/react/15.4.1/react-dom.min.js"></script>
	<script>
		window.rootPath = '';
  </script>
	<script src="/build/blogDetail.js"></script>
	<script>
    var _hmt = _hmt || [];
    (function() {
      var hm = document.createElement("script");
      hm.src = "https://hm.baidu.com/hm.js?104e73ef0c18b416b27abb23757ed8ee";
      var s = document.getElementsByTagName("script")[0];
      s.parentNode.insertBefore(hm, s);
    })();
    </script>
</body>
</html>
